<?php

namespace backend\controllers;


use backend\models\FileSettingForm;
use common\models\batch\FileBatch;
use common\models\File;
use common\models\search\FileSearch;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use Imagine\Image\Point;
use yii\base\InvalidParamException;
use Yii;
use yii\filters\VerbFilter;
use yii\imagine\Image;
use yii\web\NotFoundHttpException;
use yii\web\Response;

class FileController extends BaseController
{
    public function behaviors()
    {
        return array_merge(parent::behaviors(),
            [
                'verbs' => [
                    'class' => VerbFilter::className(),
                    'actions' => [
                        'delete' => ['post'],
                    ],
                ],
            ]);
    }

    public function actionIndex()
    {
        $batchModel = new FileBatch();
        if (isset($_POST['id'])) {
            $result = $batchModel->process($_POST['id'], $_POST);
            if (is_string($result)) {
                Yii::$app->getSession()->setFlash('success', $result);
                return $this->redirect(Yii::$app->getRequest()->getAbsoluteUrl());
            }
        }

        $searchModel = new FileSearch();

        return $this->render('index', [
            'dataProvider' => $searchModel->search($_GET),
            'searchModel' => $searchModel,
            'batchModel' => $batchModel,
        ]);
    }

    public function actionUpload($model = null, $field = null)
    {
        $this->menuRoute = false;

        if ($model === null || $field === null) {
            $modelClass = File::className();
            $field = 'file';
        } else {
            $modelClass = $model;
        }

        /** @var \yii\db\ActiveRecord $model */
        $model = new $modelClass(['scenario' => 'upload']);

        if ($model->load(Yii::$app->getRequest()->post()) && ($file = $model->upload($field))) {
            if (Yii::$app->getRequest()->isAjax) {
                return new Response([
                    'format' => Response::FORMAT_JSON,
                    'data' => [
                        'success' => true,
                        'attachment' => $file->toAttachment(),
                    ],
                ]);
            } else {
                Yii::$app->getSession()->setFlash('success', '文件上传成功。');
                return $this->redirect(['view', 'id' => $file->id]);
            }
        }

        if (Yii::$app->getRequest()->isAjax) {
            if ($model->hasErrors($field)) {
                $data = [
                    'success' => false,
                    'errors' => $model->getErrors($field),
                ];
            } else {
                $data = [];
            }
            return new Response([
                'format' => Response::FORMAT_JSON,
                'data' => $data,
            ]);
        } else {
            return $this->render('upload', [
                'model' => $model,
            ]);
        }
    }

    /**
     * 更新文件
     *
     * @param $id
     * @return string|Response
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);
        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            Yii::$app->session->setFlash('success', '文件更新成功。');
            return $this->redirect(['update', 'id' => $model->id]);
        }
        return $this->render('update', [
            'model' => $model,
        ]);
    }

    /**
     * 删除文件
     *
     * @param $id
     * @return Response
     */
    public function actionDelete($id)
    {
        $model = $this->findModel($id);
        if ($model->bundle === File::className()) {
            $model->delete()
            &&
            Yii::$app->session->setFlash('success', '文件删除成功。');
        }
        return $this->redirect(['index']);
    }

    /**
     * 删除文件
     *
     * @param $id
     * @return string
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    public function actionSettings()
    {
        $this->menuRoute = false;

        $model = new FileSettingForm();
        if ($model->load(Yii::$app->getRequest()->post()) && $model->save()) {
            Yii::$app->getSession()->setFlash('success', '媒体选项保存成功');
            return $this->redirect(['settings']);
        }

        return $this->render('settings', [
            'model' => $model,
        ]);
    }

    /**
     * 图片编辑预览
     *
     * @param $id
     * @throws \yii\base\InvalidParamException
     */
    public function actionImgeditPreview($id)
    {
        $model = $this->findModel($id);
        if (!$model->isImage()) {
            throw new InvalidParamException('文件不是图片类型');
        }

        $image = Image::getImagine()->open($model->getPath());
        $changes = !empty($_REQUEST['history']) ? json_decode($_REQUEST['history']) : null;
        if ($changes) {
            $this->imageEditApplyChanges($image, $changes);
        }
        $size = $image->getSize();
        $ratio = $this->imageEditPreviewRatio($size->getWidth(), $size->getHeight());
        $image->resize($size->scale($ratio));
        $image->show($model->getExtension());
    }

    /**
     * 获取图片编辑器
     *
     * @param $id
     * @return string|Response
     * @throws \yii\base\InvalidParamException
     */
    public function actionImageEditor($id)
    {
        $model = $this->findModel($id);
        if (!$model->isImage()) {
            throw new InvalidParamException('文件不是图片类型');
        }

        if (isset($_POST['do'])) {
            switch ($_POST['do']) {
                case 'save':
                case 'scale':
                    $image = Image::getImagine()->open($model->getPath());

                    if ($_POST['do'] === 'save') {
                        $changes = !empty($_REQUEST['history']) ? json_decode($_REQUEST['history']) : null;
                        $target = !empty($_REQUEST['target']) ? preg_replace('/[^a-z0-9_-]+/i', '', $_REQUEST['target']) : '';
                        if ($changes) {
                            $this->imageEditApplyChanges($image, $changes);
                        }
                    } else {
                        $fWidth = !empty($_REQUEST['fwidth']) ? intval($_REQUEST['fwidth']) : 0;
                        $fHeight = !empty($_REQUEST['fheight']) ? intval($_REQUEST['fheight']) : 0;

                        $box = $image->getSize();
                        $sX = $box->getWidth();
                        $sY = $box->getHeight();

                        // check if it has roughly the same w / h ratio
                        $diff = round($sX / $sY, 2) - round($fWidth / $fHeight, 2);
                        if (-0.1 < $diff && $diff < 0.1) {
                            // scale the full size image
                            $image->resize(new Box($fWidth, $fHeight));
                            $target = 'scale';
                        } else {
                            $result = array('error' => '图片拉伸失败');
                            break;
                        }
                    }

                    $result = $this->saveImage($model, $image, $target);
                    if ($_POST['do'] == 'save') {
                        return new Response([
                            'format' => Response::FORMAT_JSON,
                            'data' => $result,
                        ]);
                    }
                    break;
                case 'restore':
                    $result = $this->restoreImage($model);
                    break;
            }
        }

        $dimensions = $model->getDimensions();
        if ($dimensions === false) {
            throw new InvalidParamException('图片长宽不正确，请重新上传。');
        } else {
            $width = $dimensions['width'];
            $height = $dimensions['height'];
        }

        $sizer = $this->imageEditPreviewRatio($width, $height);

        $sizes = $model->getImageSizes();

        if (isset($sizes[File::IMAGE_THUMBNAIL])) {
            $thumbW = $sizes[File::IMAGE_THUMBNAIL]['width'];
            $thumbH = $sizes[File::IMAGE_THUMBNAIL]['height'];
        } elseif (isset($sizes[File::IMAGE_ORIGIN])) {
            $thumbW = $sizes[File::IMAGE_ORIGIN]['width'];
            $thumbH = $sizes[File::IMAGE_ORIGIN]['height'];
        } else {
            $thumbW = 160;
            $thumbH = 120;
        }

        list($thumbW, $thumbH) = $model->constrainDimensions($thumbW, $thumbH, 160, 120);

        $meta = $model->getMeta();

        $backupSizes = isset($meta['_backup_sizes']) ? $meta['_backup_sizes'] : false;
        $canRestore = false;

        if (!empty($backupSizes) && isset($backupSizes[File::IMAGE_ORIGIN . '-orig'], $model->uri)) {
            $canRestore = $backupSizes[File::IMAGE_ORIGIN . '-orig']['uri'] != $model->uri;
        }

        return $this->renderAjax('_imageEditor', [
            'id' => $id,
            'model' => $this->findModel($id),
            'canRestore' => $canRestore,
            'thumbW' => $thumbW,
            'thumbH' => $thumbH,
            'width' => $width,
            'height' => $height,
            'sizer' => $sizer,
            'result' => isset($result) ? $result : [],
        ]);
    }

    /**
     * 重新还图片
     *
     * @param \common\models\File $model
     * @return array
     */
    protected function restoreImage(File $model)
    {
        $meta = $model->getMeta();
        if (isset($meta['_backup_sizes'])) {
            $backupSizes = $meta['_backup_sizes'];
        } else {
            return [
                'error' => '没有找到图片备份信息'
            ];
        }

        $dimensions = $model->getDimensions();
        $filePath = $model->getPath();
        $parts = pathinfo($filePath);
        $suffix = time() . rand(100, 999);
        if (isset($backupSizes[File::IMAGE_ORIGIN . '-orig']) && is_array($backupSizes[File::IMAGE_ORIGIN . '-orig'])) {
            $data = $backupSizes[File::IMAGE_ORIGIN . '-orig'];

            if ($model->uri != $data['uri']) {
                if (Yii::$app->get('option')->get('file.image_edit_overwrite', false)) {
                    // delete only if it's edited image
                    if (preg_match('/-e[0-9]{13}\./', $parts['basename'])) {
                        @unlink($filePath);
                    }
                } elseif (isset($dimensions['width'], $dimensions['height'])) {
                    $backupSizes[File::IMAGE_ORIGIN . "-{$suffix}"] = ['width' => $dimensions['width'], 'height' => $dimensions['height'], 'uri' => $dimensions['uri']];
                }
            }

            $model->uri = $data['uri'];
            $meta['sizes'][File::IMAGE_ORIGIN]['uri'] = $data['uri'];
            $meta['sizes'][File::IMAGE_ORIGIN]['width'] = $data['width'];
            $meta['sizes'][File::IMAGE_ORIGIN]['height'] = $data['height'];
        }

        $defaultSizes = array_keys($model->getImageSizes());
        foreach ($defaultSizes as $name) {
            if (isset($backupSizes["{$name}-orig"])) {
                $data = $backupSizes["{$name}-orig"];
                if (isset($meta['sizes'][$name]) && $meta['sizes'][$name]['uri'] != $data['uri']) {
                    if (Yii::$app->get('options')->get('file.image_edit_overwrite', false)) {
                        // delete only if it's edited image
                        if (preg_match('/-e[0-9]{13}-/', $meta['sizes'][$name]['file'])) {
                            @unlink(File::localPath($meta['sizes'][$name]['uri']));
                        }
                    } else {
                        $backupSizes["$name-{$suffix}"] = $meta['sizes'][$name];
                    }
                }

                $meta['sizes'][$name] = $data;
            } else {
                unset($meta['sizes'][$name]);
            }
        }

        $meta['_backup_sizes'] = $backupSizes;
        $model->setMeta($meta);

        if ($model->update(false, ['file_meta', 'uri', 'updated_at'])) {
            return [
                'msg' => '图片还原成功'
            ];
        } else {
            return [
                'error' => '图片还原失败'
            ];
        }
    }

    /**
     * 保存图片编辑结果
     *
     * @param \common\models\File $model
     * @param ImageInterface $image
     * @param $target
     * @return bool
     */
    protected function saveImage(File $model, $image, $target)
    {
        $meta = $model->getMeta();
        if (isset($meta['_backup_sizes'])) {
            $backupSizes = $meta['_backup_sizes'];
        } else {
            $backupSizes = [];
        }

        if (!$dimensions = $model->getDimensions()) {
            return false;
        }

        $baseUri = str_replace(basename($model->uri), '', $model->uri);
        if ('/' !== substr($baseUri, -1)) {
            $baseUri .= '/';
        }
        $uriParts = pathinfo($model->uri);
        $filename = $uriParts['filename'];
        $suffix = time() . rand(100, 999);

        if (Yii::$app->get('option')->get('file.image_edit_overwrite', false) && $backupSizes[File::IMAGE_ORIGIN . '-orig']['uri'] != $model->uri) {
            if ($target === 'thumbnail') {
                $newUri = "{$baseUri}{$filename}-temp.{$uriParts['extension']}";
            } else {
                $newUri = $model->uri;
            }
        } else {
            while (true) {
                $filename = preg_replace('/-e([0-9]+)$/', '', $filename);
                $filename .= "-e{$suffix}";
                $newFilename = "{$filename}.{$uriParts['extension']}";
                $newUri = "{$baseUri}{$newFilename}";
                if (file_exists(File::localPath($newUri))) {
                    $suffix++;
                } else {
                    break;
                }
            }
        }

        if (isset($newUri)) {
            $image->save(File::localPath($newUri));
        } else {
            return false;
        }

        $imageSizes = $model->getImageSizes();

        if ('nothumb' == $target || 'all' == $target || 'full' == $target || 'scale' == $target) {
            $tag = false;
            if (isset($backupSizes[File::IMAGE_ORIGIN . '-orig'])) {
                if ((!Yii::$app->get('option')->get('file.image_edit_overwrite', false)) && $backupSizes[File::IMAGE_ORIGIN . '-orig']['uri'] != $model->uri) {
                    $tag = File::IMAGE_ORIGIN . "-{$suffix}";
                }
            } else {
                $tag = File::IMAGE_ORIGIN . '-orig';
            }
            if ($tag) {
                $backupSizes[$tag] = ['width' => $dimensions['width'], 'height' => $dimensions['height'], 'uri' => $dimensions['uri']];
            }

            $model->uri = $newUri;

            $size = $image->getSize();
            $meta['sizes'][File::IMAGE_ORIGIN] = [
                'width' => $size->getWidth(),
                'height' => $size->getHeight(),
                'uri' => $newUri,
            ];

            if ('nothumb' == $target || 'all' == $target) {
                $sizes = array_keys($imageSizes);
                if ('nothumb' == $target) {
                    $sizes = array_diff($sizes, [File::IMAGE_THUMBNAIL]);
                }
            }
        } elseif ('thumbnail' == $target) {
            $sizes = [File::IMAGE_THUMBNAIL];
            $delete = true;
        }

        if (isset($sizes)) {
            $_sizes = [];
            foreach ($sizes as $name) {
                $tag = false;
                if (isset($meta['sizes'][$name])) {
                    if (isset($backupSizes["{$name}-orig"])) {
                        if (!Yii::$app->get('option')->get('file.image_edit_overwrite', false) && $backupSizes["{$name}-orig"]['uri'] != $model->uri) {
                            $tag = "{$name}-{$suffix}";
                        }
                    } else {
                        $tag = "{$name}-orig";
                    }
                    if ($tag) {
                        $backupSizes[$tag] = $meta['sizes'][$name];
                    }
                }
                if (isset($imageSizes[$name])) {
                    $_sizes[$name] = [
                        $imageSizes[$name]['width'],
                        $imageSizes[$name]['height'],
                        isset($imageSizes[$name]['crop']) ? $imageSizes[$name]['crop'] : false,
                    ];
                }
            }
            $meta['sizes'] = array_merge($meta['sizes'], $this->imageMultiResize($image, $_sizes, $newUri));
        }

        $meta['_backup_sizes'] = $backupSizes;
        $model->setMeta($meta);

        if (!$model->update(false, ['file_meta', 'uri', 'update_time'])) {
            $result = ['error' => '图片保存失败'];
        } else {
            $result['thumbnail'] = $model->getUrl();

            if (isset($meta['sizes'][File::IMAGE_ORIGIN])) {
                $result['fw'] = $meta['sizes'][File::IMAGE_ORIGIN]['width'];
                $result['fh'] = $meta['sizes'][File::IMAGE_ORIGIN]['height'];
            }

            $result['msg'] = '图片保存成功';
        }

        if (!empty($delete)) {
            @unlink(File::localPath($newUri));
        }

        return $result;
    }

    /**
     * 处理图片
     *
     * @param ImageInterface $image
     * @param array $sizes
     * @param string $uri
     * @return array
     */
    protected function imageMultiResize($image, $sizes, $uri)
    {
        $metaSizes = [];
        $filePath = File::localPath($uri);
        $parts = pathinfo($filePath);
        $filename = $parts['filename'];
        $ext = $parts['extension'];

        $imageSize = $image->getSize();

        $baseUri = str_replace(basename($uri), '', $uri);
        if ('/' !== substr($baseUri, -1)) {
            $baseUri .= '/';
        }

        foreach ($sizes as $name => $size) {
            if (!isset($size[2])) {
                $size[2] = false;
            }

            list($width, $height, $crop) = $size;

            if ($imageSize->getWidth() < $width && $imageSize->getHeight() < $height) {
                continue;
            }

            if (empty($crop)) {
                $thumbnail = $image->thumbnail(new Box($width, $height));
            } else {
                $thumbnail = $image->thumbnail(new Box($width, $height), ImageInterface::THUMBNAIL_OUTBOUND);
            }

            $thumbnailImageSize = $thumbnail->getSize();

            if ($name === File::IMAGE_ORIGIN) {
                $saveFilePath = $filePath;
                $dUri = $uri;
            } else {
                $dUri = $baseUri . "{$filename}_" . $thumbnailImageSize->getWidth() . 'x' . $thumbnailImageSize->getHeight() . ".{$ext}";
                $saveFilePath = File::localPath($dUri);
            }

            $thumbnail->save($saveFilePath);
            $metaSizes[$name] = [
                'uri' => $dUri,
                'width' => $thumbnailImageSize->getWidth(),
                'height' => $thumbnailImageSize->getHeight(),
                'crop' => (bool)$crop,
            ];
        }

        return $metaSizes;
    }

    /**
     * 图片编辑应用改变
     *
     * @param ImageInterface $image
     * @param array $changes
     * @return ImageInterface
     * @throws \yii\base\InvalidParamException
     */
    protected function imageEditApplyChanges($image, $changes)
    {
        if (!$image instanceof ImageInterface) {
            throw new InvalidParamException('错误的图片资源');
        }

        if (!is_array($changes)) {
            return $image;
        }

        foreach ($changes as $key => $obj) {
            if (isset($obj->r)) {
                $obj->type = 'rotate';
                $obj->angle = $obj->r;
                unset($obj->r);
            } elseif (isset($obj->f)) {
                $obj->type = 'flip';
                $obj->axis = $obj->f;
                unset($obj->f);
            } elseif (isset($obj->c)) {
                $obj->type = 'crop';
                $obj->sel = $obj->c;
                unset($obj->c);
            }
            $changes[$key] = $obj;
        }

        // combine operations
        if (count($changes) > 1) {
            $filtered = array($changes[0]);
            for ($i = 0, $j = 1; $j < count($changes); $j++) {
                $combined = false;
                if ($filtered[$i]->type == $changes[$j]->type) {
                    switch ($filtered[$i]->type) {
                        case 'rotate':
                            $filtered[$i]->angle += $changes[$j]->angle;
                            $combined = true;
                            break;
                        case 'flip':
                            $filtered[$i]->axis ^= $changes[$j]->axis;
                            $combined = true;
                            break;
                    }
                }
                if (!$combined)
                    $filtered[++$i] = $changes[$j];
            }
            $changes = $filtered;
            unset($filtered);
        }

        foreach ($changes as $operation) {
            switch ($operation->type) {
                case 'rotate':
                    if ($operation->angle != 0) {
                        $image->rotate(-$operation->angle);
                    }
                    break;
                case 'flip':
                    if ($operation->axis == 1) {
                        $image->flipVertically();
                    } elseif ($operation->axis == 2) {
                        $image->flipHorizontally();
                    }
                    break;
                case 'crop':
                    $sel = $operation->sel;
                    $box = $image->getSize();
                    $scale = 1 / $this->imageEditPreviewRatio($box->getWidth(), $box->getHeight()); // discard preview scaling
                    $image->crop(new Point($sel->x * $scale, $sel->y * $scale), new Box($sel->w * $scale, $sel->h * $scale));
                    break;
            }
        }
        return $image;
    }

    /**
     * 获取图片编辑预览比例
     *
     * @param $width
     * @param $height
     * @return float|int
     */
    protected function imageEditPreviewRatio($width, $height)
    {
        $max = max($width, $height);
        return $max > 500 ? (500 / $max) : 1;
    }

    /**
     * @param $id
     * @return File
     * @throws \yii\web\NotFoundHttpException
     */
    protected function findModel($id)
    {
        if (($model = File::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}
